/*a small file Server
Usage: suppose Server is running on sd1.encs.concordia.ca and server is running on sd2.encs.concordia.ca
.Also suppose there is a file called test.txt on the server.
In the Server,issuse "Server sd2.encs.concordia.ca test.txt size" and you can get the size of the file.
In the Server,issuse "Server sd2.encs.concordia.ca test.txt time" and you can get creation time of the file
*/

///////////////////////////////////////////
// Authors : Mathieu Robitaille - 6396682
//           Sacha Sukhdeo - 6054412
// Class : COMP445
//         Assignment 2
///////////////////////////////////////////

#include <iostream>
#include "server.h"
#include <time.h>
#define REQUEST_PORT_SRV_IN 5001
#define REQUEST_PORT_SRV_OUT 7001

TcpServer::TcpServer()
{
	WSADATA wsadata;
	if (WSAStartup(0x0202,&wsadata)!=0)
		TcpThread::err_sys("Starting WSAStartup() error\n");
	
	//Display name of local host
	if(gethostname(servername,HOSTNAME_LENGTH)!=0) //get the hostname
		TcpThread::err_sys("Get the host name error,exit");
	std::cout << "ftpd_tcp starting at host: [" << servername << "]" << std::endl;
	
	//Create the server socket
	if ((serverSock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
		TcpThread::err_sys("Create socket error,exit");
	
	//Fill-in Server Port and Address info.
	memset(&ServerAddr, 0, sizeof(ServerAddr));      /* Zero out structure */
	ServerAddr.sin_family = AF_INET;                 /* Internet address family */
	ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);  /* Any incoming interface */
	ServerAddr.sin_port = htons(REQUEST_PORT_SRV_IN);         /* Local port */
	
	//Bind the server socket
    if (bind(serverSock, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr)) == INVALID_SOCKET)
		TcpThread::err_sys("Bind socket error,exit. Code %i", WSAGetLastError());
}

TcpServer::~TcpServer()
{
    closesocket(serverSock);
	WSACleanup();
}

void TcpServer::start()
{
    std::cout << "Waiting to be contacted for transfering files..." << std::endl;
	while (true) /* Run forever */
	{
		/* Create a Thread for this new connection and run*/
		TcpThread pt(serverSock);
		pt.run();
	}
}

//////////////////////////////TcpThread Class //////////////////////////////////////////

void TcpThread::run() //cs: Server socket
{
	srand((unsigned)time(NULL));
    gethostname(m_hostname, HOSTNAME_LENGTH);

    memset(&m_clientAddr, 0, sizeof(SOCKADDR_IN));
    int sockAddrInSize = sizeof(SOCKADDR_IN);
    int val2 = getsockname(cs, (SOCKADDR*)&m_clientAddr, &sockAddrInSize);
	m_clientAddr.sin_family = AF_INET;						// Internet address family 
	m_clientAddr.sin_addr.s_addr = htonl(INADDR_ANY);		// Any incoming interface 
	m_clientAddr.sin_port = htons(REQUEST_PORT_SRV_OUT);    // Local port
	cs = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

	Req * reqp; //a pointer to the Request Packet
	Msg rmsg; //send_message receive_message
	FD_SET fdset;
	FD_ZERO(&fdset);
	FD_SET(ss, &fdset);
	//FD_SET(cs, &fdset);

	memset(&rmsg, 0, sizeof(rmsg));

	if (WaitForSocket(fdset, NULL)) {
        SOCKADDR_IN addr;
		msg_recv(ss, &rmsg, (LPSOCKADDR)&addr);
        m_clientAddr.sin_addr = addr.sin_addr;
    }
    connect(cs, (LPSOCKADDR)&m_clientAddr, sockAddrInSize);

///-------------------------------------------------------------------------------------------------Sacha.start

	if(rmsg.length >0) {
		HandShakeMsg &handShakeMsg = *(HandShakeMsg*)rmsg.buffer;

		if(handShakeMsg.handShakeSTEP == FIRST_STEP_SHAKE ) {

			std::cout << "FIRST_STEP_SHAKE  Received" << std::endl;
						
			localSeqNb = rand() % 256; // generate random number between 0 and 255
			remoteSeqNb = handShakeMsg.Ack;

			HandShakeMsg handShakeMsgSecond;
			memset(&handShakeMsgSecond, 0, sizeof(handShakeMsgSecond));
			handShakeMsgSecond.Ack = localSeqNb;
			handShakeMsgSecond.ReturnAck = handShakeMsg.Ack;
			handShakeMsgSecond.handShakeSTEP = SECOND_STEP_SHAKE;

			Msg msg;
			memset(&msg, 0, sizeof(msg));
			msg.type= HANDSHAKE;
			msg.length = sizeof(handShakeMsgSecond);
			memcpy(msg.buffer, &handShakeMsgSecond, msg.length);

			msg_send(cs, &msg, (SOCKADDR*)&m_clientAddr, sockAddrInSize);

			if (WaitForSocket(fdset, &TcpBase::TIMEOUT)) {
				msg_recv(ss, &rmsg);
				HandShakeMsg &handShakeMsgRec = *(HandShakeMsg*)rmsg.buffer;
				if(handShakeMsgRec.handShakeSTEP == THIRD_STEP_SHAKE) {
					if(handShakeMsgRec.ReturnAck == localSeqNb)
						std::cout << "3 WAY HANDSHAKE COMPLETE"  << std::endl;
					else {
						std::cerr << "3 WAY HANDSHAKE NOT COMPLETE INVALID RETURN ACK"  << std::endl;
						return;
					}
				} else {
					std::cerr << "MSG received not identified as THIRD STEP SHAKE"  << std::endl;
					return;
				}
			} else
				std::cerr << "THIRD_STEP_SHAKE not received"  << std::endl;
		}
	//------------------------------------------------------------------------------------------------------Sacha.end
	} else {
        closesocket(cs);
        return;
    }

	memset(&m_serverAddr, 0, sizeof(m_serverAddr));      /* Zero out structure */
	m_serverAddr.sin_family = AF_INET;                 /* Internet address family */
	m_serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);  /* Any incoming interface */
	m_serverAddr.sin_port = htons(REQUEST_PORT_SRV_IN);         /* Local port */

    //////////////////////////////////////////////////////////////
    // Make sure all the data is sent before closing the socket //
    LINGER l;
    memset(&l, 0, sizeof(l));
    l.l_onoff = 1;
    l.l_linger = 1;
    setsockopt(/*cs*/ss, SOL_SOCKET, SO_LINGER, (char*)&l, sizeof(l));
    //////////////////////////////////////////////////////////////
    LogInfo li;
    memset(&li, 0, sizeof(li));
	rmsg = ReceivePacket(ss, cs, (LPSOCKADDR)&m_clientAddr, li);
	//cast it to the request packet structure		
	reqp=(Req *)rmsg.buffer;

    switch(rmsg.type)
    {
	case REQ_GET:
        {
		    std::cout << "User \"" << reqp->username << "\" requested file " << reqp->filename <<std::endl;
		    SendFile(reqp->filename, cs, ss, reqp->hostname, (SOCKADDR*)&m_clientAddr, sockAddrInSize);
        }
        break;
    case REQ_PUT:
	    {
		    std::cout << "User \"" << reqp->username << "\" is sending file " << reqp->filename <<std::endl;
		    ReceiveFile(reqp->filename, cs, ss, (SOCKADDR*)&m_clientAddr, reqp->hostname);
	    }
        break;
    case REQ_DEL:
        {
            std::cout << "User \"" << reqp->username << "\" wants to delete file " << reqp->filename << std::endl;
            int r = DeleteFile(reqp->filename);
            Msg msg;
            memset(&msg, 0, sizeof(msg));
            if (r == 0) {
                char m[256];
                int n;
                if ((n = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, (LPSTR)m, 256, NULL)) == 0)
                    err_sys("Error formatting an error message...");
                msg.type = MSG_ERR;
                std::cerr << "File " << reqp->filename << " could not be deleted." << std::endl;
                memset(msg.buffer, 0, sizeof(msg.buffer));
                memcpy(msg.buffer, m, n);
                msg.length = n+1;
            } else {
                msg.type = RESP;
                std::cout << "File " << reqp->filename << " deleted successfully." << std::endl;
                msg.length = 0;
            }
            SendPacket(cs, ss, msg, (LPSOCKADDR)&m_clientAddr, sizeof(SOCKADDR_IN), li, true);
        }
        break;
    case REQ_LIST:
	    {
            std::cout << "User \"" << reqp->username << "\" requested the file list in directory '" << reqp->filename << "'" << std::endl;
		    SendFileList(reqp->filename, cs, ss, (SOCKADDR*)&m_clientAddr, sockAddrInSize);
	    }
        break;
    }
	closesocket(cs);
}

////////////////////////////////////////////////////////////////////////////////////////

int main(void)
{
	TcpServer ts;
	ts.start();
	return 0;
}
